#include "StdAfx.h"
#include "BurgerTimeMachine.h"
#include "cpu/Dead6502v3.h"
#include "cpu/dead6502v4.h"

#define my6502		CDead6502v3
#define P(nn)		((nn) / 0x800)

void CBurgerTimeMachine::WritePalette(u32 addr,u8 data)
{
u8 r,g,b;

data = ~data;
r = (u8)((0x21 * ((data >> 0) & 1)) + (0x47 * ((data >> 1) & 1)) + (0x97 * ((data >> 2) & 1)));
g = (u8)((0x21 * ((data >> 3) & 1)) + (0x47 * ((data >> 4) & 1)) + (0x97 * ((data >> 5) & 1)));
b = (u8)((0x47 * ((data >> 6) & 1)) + (0x97 * ((data >> 7) & 1)));
Palette[addr] = (r << 16) | (g << 8) | b;
}

void CBurgerTimeMachine::WriteSoundControl(u8 data)
{
SoundControl = data;
if(SoundIRQControl != 0)
	SoundCPU->IRQ();
}

//needs to be generalized and put in CMachine
void CBurgerTimeMachine::CheckHighScores()
{
highscore_t *high = RomSet->GetHighScoreInfo();
int n;
CHighScore h;
u8 b1,b2;

if(high == 0)
	return;
for(n=0;high->Length != -1;high++)
	{
	b1 = MainCPU->Read(high->Addr);
	b2 = MainCPU->Read(high->Addr + high->Length - 1);
	if(b1 != high->Wait1 || b2 != high->Wait2)		//wait bytes dont match
		n++;														//increase number of non-matches
	}
//message("%d non-matches\n",n);
if(n == 0)
	{
	HighScoresLoaded++;
	LoadScores();
	}
}

void CBurgerTimeMachine::btimeDecrypt()
{
u32 addr = MainCPU->GetNextOpcodePC();
u32 tmp = MainCPU->GetCurrentOpcodePC();

if(NewROM[tmp] == 0x20)
	addr = MainROM[tmp + 1] | (MainROM[tmp + 2] << 8);
if((addr & 0x0104) == 0x0104)
	NewROM[addr] = (u8)
		((MainROM[addr] & 0x13) |
		((MainROM[addr] & 0x80) >> 5) |
		((MainROM[addr] & 0x64) << 1) |
		((MainROM[addr] & 0x08) << 2));
}

CBurgerTimeMachine::CBurgerTimeMachine()
{
CPU[0] = new my6502();
CPU[1] = new my6502();
MainCPU = (C6502*)CPU[0];
SoundCPU = (C6502*)CPU[1];
memset(Screen,0,256*256);
AddLoadInfo(ROM_CPU1,MainROM);
AddLoadInfo(ROM_CPU2,SoundROM);
AddLoadInfo(ROM_PROM,PROM);
AddLoadInfo(ROM_TEMP,Temp);
AddLoadInfo(ROM_VROM1,CHRROM);
AddLoadInfo(ROM_VROM2,BGROM);
AddLoadInfo(ROM_VROM3,VROM);
AddLoadInfo(ROM_VROM4,VROM2);
}

CBurgerTimeMachine::~CBurgerTimeMachine()
{
Kill();
}

int CBurgerTimeMachine::Init()
{
MainCPU->Init();
SoundCPU->Init();
MainCPU->SetUser(this);
SoundCPU->SetUser(this);
MainCPU->SetRead(ReadMainCPU);
MainCPU->SetWrite(WriteMainCPU);
MainCPU->SetOp(OpMainCPU);
SoundCPU->SetRead(ReadSoundCPU);
SoundCPU->SetWrite(WriteSoundCPU);
SoundCPU->SetOp(ReadSoundCPU);
AY8910_InitClock(1500000);
AY8910_InitAll(1500000,44100);	//2x 1.5mhz, 44100hz output
return(0);
}

void CBurgerTimeMachine::Kill()
{
Sound->SetCallback(0);
}

void CBurgerTimeMachine::Reset()
{
int i,n;

for(n=0;n<0x10000;n++)
	MainROM[n] = SoundROM[n] = 0;
LoadROMs();
for(n=0;n<0x10000;n++)
	NewROM[n] = MainROM[n];
ProcessInput();
VideoControl = 0;
SoundControl = 0;
SoundIRQControl = 0;
Scroll = 0;
Scanline = 0;
VBlank = 0x80;
for(i=0;i<0x800;i++)
	{
	MainRAM[i] = 0;
	VideoRAM[i & 0x3FF] = 0;
	ColorRAM[i & 0x3FF] = 0;
	PaletteRAM[i & 0xF] = 0;
	Palette[i & 0xF] = 0;
	}
Sound->SetCallback(ProcessSound);
AY8910_reset(0);
AY8910_reset(1);
MainCPU->Reset();
SoundCPU->Reset();
//HighScoresLoaded = 0;
//if(RomSet->GetHighScoreInfo() == 0)
//	HighScoresLoaded++;
message("machine reset\n");
}

void CBurgerTimeMachine::Blit()
{
Video->StartBlit();
RenderScreen(Screen,256);
BlitRotate(Screen,(u32*)Video->GetSurface(),Palette);
Video->StopBlit();
}

void CBurgerTimeMachine::Line(u32 mainc,u32 soundc,int irq)
{
if(Scanline < 256 && (Scanline & 0xF) == 0 && irq)
	{
	if(SoundIRQControl)
		SoundCPU->NMI();
	}
if(Scanline == 0)
	VBlank = 0;
if(Scanline == 240)
	{
	Blit();
	ProcessInput();
	VBlank = 0x80;
	}
if(MainCPU->Execute(mainc) & BAD_OPCODE) //main cpu (1500000 / 57) cycles per frame
	{
	message("main cpu: bad opcode at $%04X ($%02X)\n",MainCPU->GetPC(),MainCPU->GetOpcode());
	return;
	}
if(SoundCPU->Execute(soundc) & BAD_OPCODE) //sound cpu (500000 / 57) cycles per frame
	{
	message("sound cpu: bad opcode at $%04X ($%02X)\n",SoundCPU->GetPC(),SoundCPU->GetOpcode());
	return;
	}
Scanline++;
if(Scanline >= 272)
	Scanline = 0;
//if(HighScoresLoaded == 0)
//	CheckHighScores();
}

void CBurgerTimeMachine::Frame()
{
do
	{
//	Line(97,33,1);
	Line(97,50,1);						//speed up sound cpu
	} while(Scanline != 0);
}

void CBurgerTimeMachine::SaveState(CState *s)
{
int n;

MainCPU->SaveState(s);
SoundCPU->SaveState(s);
s->Write(MainRAM,0x800);
s->Write(SoundRAM,0x400);
s->Write(PaletteRAM,0x10);
s->Write(VideoRAM,0x400);
s->Write(ColorRAM,0x400);
s->Write8(VideoControl);
s->Write8(SoundControl);
s->Write8(Scroll);
s->Write8(VBlank);
for(n=0;n<0x10;n++)
	s->Write32(Palette[n]);
s->Write((u8*)AY8910_GetRegsPtr(0),16);
s->Write((u8*)AY8910_GetRegsPtr(1),16);
}

void CBurgerTimeMachine::LoadState(CState *s)
{
int n;
u8 ay8910_regs[2][16];

MainCPU->LoadState(s);
SoundCPU->LoadState(s);
s->Read(MainRAM,0x800);
s->Read(SoundRAM,0x400);
s->Read(PaletteRAM,0x10);
s->Read(VideoRAM,0x400);
s->Read(ColorRAM,0x400);
VideoControl = s->Read8();
SoundControl = s->Read8();
Scroll = s->Read8();
VBlank = s->Read8();
for(n=0;n<0x10;n++)
	Palette[n] = s->Read32();
s->Read(ay8910_regs[0],16);
s->Read(ay8910_regs[1],16);
for(n=0;n<16;n++)
	{
	_AYWriteReg(0,n,ay8910_regs[0][n]);
	_AYWriteReg(1,n,ay8910_regs[1][n]);
	}
}

static char highscoreident[] = "HIGHSCORE\0";
static int highscoreidentlen = 10;

void CBurgerTimeMachine::SaveScores()
{
highscore_t *high = RomSet->GetHighScoreInfo();
int n;
CHighScore h;

if(high == 0)
	return;
if(h.Save(RomSet->GetName()) != 0)
	return;
h.Write((u8*)highscoreident,highscoreidentlen);
while(high->Length != -1)
	{
	for(n=0;n<high->Length;n++)
		h.Write8(MainCPU->Read(high->Addr + n));
	high++;
	}
h.Close();
}

void CBurgerTimeMachine::LoadScores()
{
highscore_t *high = RomSet->GetHighScoreInfo();
char ident[16];
int n;
CHighScore h;

if(high == 0)
	return;
if(h.Load(RomSet->GetName()) != 0)
	return;
h.Read((u8*)ident,highscoreidentlen);
if(memcmp(ident,highscoreident,highscoreidentlen) != 0)
	{
	message("bad high score identifier (%s)\n",ident);
	return;
	}
while(high->Length != -1)
	{
	for(n=0;n<high->Length;n++)
		MainCPU->Write(high->Addr + n,h.Read8());
	high++;
	}
h.Close();
}

void CBurgerTimeMachine::RenderScreen(u8 *dest,int pitch)
{
if(Scroll & 0x10)
	RenderBackground(dest,pitch,8);
else
	memset(dest,0,256*pitch);
RenderChars(dest,pitch);
RenderSprites(dest,pitch);
}

//TODO: rewrite all three of these!

// render background layer
void CBurgerTimeMachine::RenderBackground(u8 *dest,int pitch,int oor)
{
int i,start,x,y;
u8 *b[3];
int tilemap[4];

b[0] = BGROM + 0x0000;
b[1] = BGROM + 0x0800;
b[2] = BGROM + 0x1000;
if(VideoControl & 1)
	start = 0;
else
	start = 1;
for(i=0;i<4;i++)
	{
	tilemap[i] = start | (Scroll & 4);
	start = ++start & 3;
	}
start = tilemap[Scroll & 3] * 0x100;
for(i=0;i<0x100;i++)
	{
	x = 240 - (16 * (i / 16) + 0);
	y = 16 * (i % 16);
	if(VideoControl & 1)
		{
		x = 240 - x;
		y = 240 - y;
		}
	drawtile_16x16_or_notrans(dest,pitch,VROM[start + i],(u8)x,(u8)y,b,oor);
	}
}

// render sprite layer
void CBurgerTimeMachine::RenderSprites(u8 *dest,int pitch)
{
int offs,i,x,y;
u8 *b[3];
u8 *V = VideoRAM;

b[0] = CHRROM + 0x0000;
b[1] = CHRROM + 0x2000;
b[2] = CHRROM + 0x4000;
for(i=0,offs=0;i<8;i++,offs+=4*32) //draw 8 sprites
	{
	int flip;

	if((V[offs] & 1) == 0)
		continue;
	x = 240 - V[offs + (3 * 32)];
	y = 240 - V[offs + (2 * 32)];
	flip = V[offs] & 6;
	if(VideoControl & 1)
		{
		x = 240 - x;
		y = 240 - y;
		flip = ~flip;
		}
	y -= 1;
	switch(flip & 6)
		{
		case 0:drawtile_16x16(dest,pitch,V[offs + 32],(u8)x,(u8)y,b);break;
		case 2:drawtile_16x16_flipy(dest,pitch,V[offs + 32],(u8)x,(u8)y,b);break;
		case 4:drawtile_16x16_flipx(dest,pitch,V[offs + 32],(u8)x,(u8)y,b);break;
		case 6:drawtile_16x16_flipxy(dest,pitch,V[offs + 32],(u8)x,(u8)y,b);break;
		}
	}
}

// render character layer
void CBurgerTimeMachine::RenderChars(u8 *dest,int pitch,int mask,int oor)
{
int offs,x,y,tilenum;
u8 *b[3];

b[0] = CHRROM + 0x0000;
b[1] = CHRROM + 0x2000;
b[2] = CHRROM + 0x4000;
for(offs=0;offs<0x400;offs++) //draw screen stuff
	{
	tilenum = VideoRAM[offs] + 256 * (ColorRAM[offs] & 3);
	if(tilenum & mask)
		continue;
	x = 31 - (offs / 32);
	y = offs % 32;
	if(VideoControl & 1)
		{
		x = 31 - x;
		y = 31 - y;
		}
	if(x > 0 && x < 31 && y > 0 && y < 31) //dont render the garbage
		drawtile_8x8_or(dest,pitch,tilenum,(u8)(x * 8),(u8)(y * 8),b,oor);
	}
}

u8 CBurgerTimeMachine::OpMainCPU(void *user,u32 addr)
{
CBurgerTimeMachine *M = (CBurgerTimeMachine*)user;

return(M->NewROM[addr]);
}

u8 CBurgerTimeMachine::ReadMainCPU(void *user,u32 addr)
{
CBurgerTimeMachine *M = (CBurgerTimeMachine*)user;

switch(addr & 0xF000)
	{
	case 0x0000:
		if(addr < 0x800)
			return(M->MainRAM[addr]);
		else if((addr & 0xCE0) == 0xC00)
			return(M->PaletteRAM[addr & 0x1F]);
		break;
	case 0x1000:
		if(addr < 0x1400)
			return(M->VideoRAM[addr & 0x3FF]);
		else if(addr < 0x1800)
			return(M->ColorRAM[addr & 0x3FF]);
		else if(addr < 0x1C00)
			{
			addr &= 0x3FF;
			return(M->VideoRAM[MirrorRAMAddr(addr)]);
			}
		else
			{
			addr &= 0x3FF;
			return(M->ColorRAM[MirrorRAMAddr(addr)]);
			}
		break;
	case 0x4000:
		switch(addr)
			{
			case 0x4000: //player 1 input
				return(M->InputData[0]);
			case 0x4001: //player 2 input
				return(M->InputData[1]);
			case 0x4002: //coin/start input
				return(M->InputData[2]);
			case 0x4003: //dip switch 1
				return(M->VBlank | (M->Dip[0] & 0x7F));
			case 0x4004: //dip switch 2
				return(M->Dip[1]);
			}
		break;
	case 0xB000:
	case 0xC000:
	case 0xD000:
	case 0xE000:
	case 0xF000:
		return(M->MainROM[addr]);
	}
message("unhandled read (main cpu, addr $%04X)\n",addr);
return(0);
}

void CBurgerTimeMachine::WriteMainCPU(void *user,u32 addr,u8 data)
{
CBurgerTimeMachine *M = (CBurgerTimeMachine*)user;

switch(addr & 0xFC00)
	{
	case 0x0000: //ram
	case 0x0400:
		M->MainRAM[addr] = data;
		break;
	case 0x1000: //video ram
		M->VideoRAM[addr & 0x3FF] = data;
		break;
	case 0x1400: //color ram
		M->ColorRAM[addr & 0x3FF] = data;
		break;
	case 0x1800: //video ram mirror
		addr &= 0x3FF;
		M->VideoRAM[MirrorRAMAddr(addr)] = data;
		break;
	case 0x1C00: //color ram mirror
		addr &= 0x3FF;
		M->ColorRAM[MirrorRAMAddr(addr)] = data;
		break;
	case 0x4000:
		switch(addr)
			{
			case 0x4000: //?
				break;
			case 0x4002: //video control
				M->VideoControl = data;
				break;
			case 0x4003: //sound control
				M->WriteSoundControl(data);
				break;
			case 0x4004: //scroll control
				M->Scroll = data;
				break;
			default:
				message("unhandled write (main cpu, addr $%04X = $%02X)\n",addr,data);
				break;
			}
		break;
	case 0x0C00:
		if(addr < 0xC20) //palette ram crap
			{
			addr &= 0x1F;
			M->PaletteRAM[addr] = data;
			M->WritePalette(addr,data);
			break;
			}
	default:
		message("unhandled write (main cpu, addr $%04X = $%02X)\n",addr,data);
		break;
	}
M->btimeDecrypt();
}

u8 CBurgerTimeMachine::ReadSoundCPU(void *user,u32 addr)
{
CBurgerTimeMachine *M = (CBurgerTimeMachine*)user;

switch(addr & 0xF000)
	{
	case 0x0000:
		if(addr < 0x400)				//ram
			return(M->SoundRAM[addr]);
		break;
	case 0xA000:						//sound latch
		return(M->SoundControl);
	case 0xF000:						//rom
		return(M->SoundROM[addr]);
	}
message("unhandled read (sound cpu, addr $%04X)\n",addr);
return(0);
}

void CBurgerTimeMachine::WriteSoundCPU(void *user,u32 addr,u8 data)
{
CBurgerTimeMachine *M = (CBurgerTimeMachine*)user;

switch(addr & 0xF000)
	{
	case 0x0000:
		if(addr >= 0x0400)
			message("unhandled write (sound cpu, addr $%04X = $%02X)\n",addr,data);
		else
			M->SoundRAM[addr] = data;
		break;
	case 0x2000:	AY8910Write(0,1,data);	break;
	case 0x4000:	AY8910Write(0,0,data);	break;
	case 0x6000:	AY8910Write(1,1,data);	break;
	case 0x8000:	AY8910Write(1,0,data);	break;
	case 0xC000:							//irq enable
		M->SoundIRQControl = data;
		message("sound: irq control write = $%02X\n",data);
		break;
	default:
		message("unhandled write (sound cpu, addr $%04X = $%02X)\n",addr,data);
		break;
	}
}

void CBurgerTimeMachine::ProcessSound(void *user,void *data,int len)
{
s16 *dest = (s16*)data;
s16 b1[3][1024];
s16 b2[3][1024];
int n;

AY8910Update(0,(INT16*)b1[0],(INT16*)b1[1],(INT16*)b1[2],len);
AY8910Update(1,(INT16*)b2[0],(INT16*)b2[1],(INT16*)b2[2],len);
for(n=0;n<len;n++)
	dest[n] = (b1[0][n] + b1[1][n] + b1[2][n] + b2[0][n] + b2[1][n] + b2[2][n]) / 6;
}
